/*
Copyright (C) 2012 John Tsiombikas <nuclear@member.fsf.org>,
				   Ioannis Makris <imak@imak.gr>

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdlib.h>
#include <ctype.h>
#include <X11/Xlib.h>
#ifdef USE_XRANDR
#include <X11/extensions/Xrandr.h>
#endif
#include <GL/glx.h>
#include "glory.h"
#include "gloin.h"
#include "wsys.h"
#include "log.h"

#define PENDING_REDISP	1
#define PENDING_CREATE	2


struct wsys_window {
	Window win;
	GLXContext ctx;
	int width, height;
	int mapped;
	unsigned int pending;
};

static void print_visual_info(XVisualInfo *vis);
static void attr_to_glxattr(int *glxattr, struct gloin_attr *gattr);
static void mask_to_glxattr(int *glxattr, unsigned int mask);
static int handle_event(XEvent *xev);
static void process_key(KeySym sym, int state);
static int translate_keysym(KeySym sym);
static long xevent_mask(unsigned int cbmask);

/* video mode stuff */
static int xrandr_resize(int xsz, int ysz, int rate);
static int xrandr_current(int *xsz, int *ysz, int *rate);

static int xf86vm_resize(int xsz, int ysz, int rate);
static int xf86vm_current(int *xsz, int *ysz, int *rate);

static const char *xev_name(int type);

static Display *dpy;
static Atom xa_wm_prot, xa_wm_del_win;
static int quit;	/* quit the main loop */

static unsigned int modkeys;

static int debug;


int gloin_wsys_init(void)
{
	if(dpy) {
		gloin_log("gloin_wsys_init: already initialized!\n");
		return 0;
	}

	if(!(dpy = XOpenDisplay(0))) {
		gloin_log("gloin_wsys_init: failed to connect to the X server\n");
		return -1;
	}
	xa_wm_prot = XInternAtom(dpy, "WM_PROTOCOLS", False);
	xa_wm_del_win = XInternAtom(dpy, "WM_DELETE_WINDOW", False);

	if(getenv("GLORY_X11_DEBUG")) {
		debug = 1;
	}
	return 0;
}

void gloin_wsys_cleanup(void)
{
	if(!dpy) return;

	XCloseDisplay(dpy);
	dpy = 0;
}

int gloin_wsys_set_video_mode(int xsz, int ysz, int rate)
{
	/* first try with XR&R extension */
	if(xrandr_resize(xsz, ysz, rate) == 0) {
		return 0;
	}
	/* then fallback to XF86VidMode */
	return xf86vm_resize(xsz, ysz, rate);
}

int gloin_wsys_get_video_mode(int *xsz, int *ysz, int *rate)
{
	if(xrandr_current(xsz, ysz, rate) == 0) {
		return 0;
	}
	return xf86vm_current(xsz, ysz, rate);
}

int gloin_wsys_create_window(struct window *win, int xsz, int ysz, unsigned int mode)
{
	int scr, glxattr[32];
	Window w, root;
	GLXContext ctx;
	XVisualInfo *vis;
	XClassHint chint;
	XSetWindowAttributes xattr;
	unsigned int xattr_mask;
	long evmask;
	struct wsys_window *wswin;

	if(!(wswin = malloc(sizeof *wswin))) {
		gloin_log("gloin_wsys_create_window: failed to allocate memory\n");
		return -1;
	}

	if(mode == GLORY_USE_ATTR) {
		attr_to_glxattr(glxattr, &gloin_attr);
	} else {
		mask_to_glxattr(glxattr, mode);
	}

	/* TODO take the glory_attr_screen into account */
	scr = DefaultScreen(dpy);
	root = RootWindow(dpy, scr);

	if(!(vis = glXChooseVisual(dpy, scr, glxattr))) {
		gloin_log("gloin_wsys_create_window: failed to find a suitable visual\n");
		free(wswin);
		return -1;
	}
	print_visual_info(vis);

	if(!(ctx = glXCreateContext(dpy, vis, 0, True))) {
		gloin_log("gloin_wsys_create_window: failed to create OpenGL context\n");
		XFree(vis);
		free(wswin);
		return -1;
	}

	xattr.background_pixel = xattr.border_pixel = BlackPixel(dpy, scr);
	xattr.colormap = XCreateColormap(dpy, root, vis->visual, AllocNone);
	xattr_mask = CWColormap | CWBackPixel | CWBorderPixel;

	if(!(w = XCreateWindow(dpy, root, 0, 0, xsz, ysz, 0, vis->depth, InputOutput,
					vis->visual, xattr_mask, &xattr))) {
		gloin_log("failed to create window\n");
		glXDestroyContext(dpy, ctx);
		XFree(vis);
		free(wswin);
		return -1;
	}
	XFree(vis);

	evmask = StructureNotifyMask | VisibilityChangeMask;
	XSelectInput(dpy, w, evmask);

	XSetWMProtocols(dpy, w, &xa_wm_del_win, 1);

	chint.res_name = chint.res_class = "glorywin";
	XSetClassHint(dpy, w, &chint);

	XMapWindow(dpy, w);

	wswin->win = w;
	wswin->ctx = ctx;
	wswin->width = xsz;
	wswin->height = ysz;
	wswin->mapped = 0;
	wswin->pending = PENDING_REDISP | PENDING_CREATE;

	win->wsys_win = wswin;

	gloin_wsys_set_title(win, "Glorious Window (X11)");
	return 0;
}

void gloin_wsys_destroy_window(struct window *win)
{
	void (*func)() = gloin_get_callback(win, GLORY_DESTROY);
	if(func) {
		gloin_set_active(win);
		func(gloin_get_callback_data(win, GLORY_DESTROY));
	}

	glXDestroyContext(dpy, win->wsys_win->ctx);
	XDestroyWindow(dpy, win->wsys_win->win);

	free(win->wsys_win);
	win->wsys_win = 0;
}

void gloin_wsys_resize(struct window *win, int xsz, int ysz)
{
	XResizeWindow(dpy, win->wsys_win->win, xsz, ysz);
}

void gloin_wsys_set_size(struct window *win, int xsz, int ysz)
{
	win->wsys_win->width = xsz;
	win->wsys_win->height = ysz;
}

void gloin_wsys_get_size(struct window *win, int *xsz, int *ysz)
{
	*xsz = win->wsys_win->width;
	*ysz = win->wsys_win->height;
}


void gloin_wsys_set_active(struct window *win)
{
	if(win) {
		glXMakeCurrent(dpy, win->wsys_win->win, win->wsys_win->ctx);
	} else {
		glXMakeCurrent(dpy, 0, 0);
	}
}

void gloin_wsys_resize_window(struct window *win, int width, int height)
{
	XResizeWindow(dpy, win->wsys_win->win, width, height);
}

void gloin_wsys_set_title(struct window *win, const char *str)
{
	XTextProperty wm_name;
	XStringListToTextProperty((char**)&str, 1, &wm_name);
	XSetWMName(dpy, win->wsys_win->win, &wm_name);
	XSetWMIconName(dpy, win->wsys_win->win, &wm_name);
	XFree(wm_name.value);
}

void gloin_wsys_redisplay(struct window *win)
{
	win->wsys_win->pending |= PENDING_REDISP;
}

void gloin_wsys_swap_buffers(struct window *win)
{
	glXSwapBuffers(dpy, win->wsys_win->win);
}

unsigned int gloin_wsys_modifiers(void)
{
	return modkeys;
}

void gloin_wsys_set_callback_mask(struct window *win, unsigned int cbmask)
{
	long evmask = xevent_mask(cbmask);
	XSelectInput(dpy, win->wsys_win->win, evmask);
}

void gloin_wsys_quit(void)
{
	quit = 1;
}

/* run a single iteration of the event loop */
int glory_process_events(void)
{
	XEvent xev;
	int have_idle;
	struct window *win;

	/* loop through all the windows to call any pending create callbacks
	 * and determine if there are any idle callbacks registered or not
	 */
	win = gloin_winlist();
	while(win) {
		/* check if there's a pending create call (the window was just created) */
		if(win->wsys_win->pending & PENDING_CREATE) {
			gloin_call_create(win);
			/* reset the create-pending bit */
			win->wsys_win->pending &= ~PENDING_CREATE;
		}
		win = win->next;
	}

	/* If there are no idle callbacks registered, we should block */
	have_idle = gloin_have_idle();
	if(!have_idle) {
		XNextEvent(dpy, &xev);
		XPutBackEvent(dpy, &xev);
	}

	while(XPending(dpy)) {
		XNextEvent(dpy, &xev);
		if(quit || handle_event(&xev) == -1) {
			return -1;
		}
	}

	/* second loop through all windows, to call any pending redisplays */
	win = gloin_winlist();
	while(win) {
		if(win->wsys_win->pending & PENDING_REDISP) {
			void (*func)() = gloin_get_callback(win, GLORY_DISPLAY);
			if(func) {
				gloin_set_active(win);
				func(gloin_get_callback_data(win, GLORY_DISPLAY));
			}
			win->wsys_win->pending &= ~PENDING_REDISP;
		}
		win = win->next;
	}

	/* call any idle callbacks */
	if(have_idle) {
		gloin_call_idle();
	}
	return 0;
}

void gloin_wsys_event_loop(void)
{
	while(!quit) {
		if(glory_process_events() == -1) {
			break;
		}
	}
}

static void attr_to_glxattr(int *glxattr, struct gloin_attr *gattr)
{
	int i = 0;

	glxattr[i++] = GLX_RGBA;
	glxattr[i++] = GLX_RED_SIZE; glxattr[i++] = gattr->rbits;
	glxattr[i++] = GLX_GREEN_SIZE; glxattr[i++] = gattr->gbits;
	glxattr[i++] = GLX_BLUE_SIZE; glxattr[i++] = gattr->bbits;
	glxattr[i++] = GLX_DEPTH_SIZE; glxattr[i++] = gattr->zbits;
	glxattr[i++] = GLX_STENCIL_SIZE; glxattr[i++] = gattr->sbits;

	if(gattr->doublebuf) {
		glxattr[i++] = GLX_DOUBLEBUFFER;
	}
	if(gattr->stereo) {
		glxattr[i++] = GLX_STEREO;
	}

#if defined(GLX_VERSION_1_4) || defined(GLX_ARB_multisample)
	if(gattr->msamples > 0) {
		glxattr[i++] = GLX_SAMPLE_BUFFERS_ARB;
		glxattr[i++] = 1;
		glxattr[i++] = GLX_SAMPLES_ARB;
		glxattr[i++] = gattr->msamples;
	}
#endif	/* defined GLX_VERSION_1_4 || GLX_ARB_multisample */

	glxattr[i++] = None;
}

static void mask_to_glxattr(int *attr, unsigned int mask)
{
	int i = 0;

	attr[i++] = GLX_RGBA;
	attr[i++] = GLX_RED_SIZE; attr[i++] = 1;
	attr[i++] = GLX_GREEN_SIZE; attr[i++] = 1;
	attr[i++] = GLX_BLUE_SIZE; attr[i++] = 1;

	if(mask & GLORY_DOUBLE) {
		attr[i++] = GLX_DOUBLEBUFFER;
	}
	if(mask & GLORY_DEPTH) {
		attr[i++] = GLX_DEPTH_SIZE;
		attr[i++] = 1;
	}
	if(mask & GLORY_STENCIL) {
		attr[i++] = GLX_STENCIL_SIZE;
		attr[i++] = 1;
	}
	if(mask & GLORY_STEREO) {
		attr[i++] = GLX_STEREO;
	}
#if defined(GLX_VERSION_1_4) || defined(GLX_ARB_multisample)
	if(mask & GLORY_MULTISAMPLE) {
		attr[i++] = GLX_SAMPLE_BUFFERS_ARB;
		attr[i++] = 1;
		attr[i++] = GLX_SAMPLES_ARB;
		attr[i++] = 1;
	}
#endif	/* defined GLX_VERSION_1_4 || GLX_ARB_multisample */
	attr[i] = None;
}

static void print_visual_info(XVisualInfo *vis)
{
	int rbits, gbits, bbits, zbits, sbits, stereo, aa, samples = 1;

	glXGetConfig(dpy, vis, GLX_RED_SIZE, &rbits);
	glXGetConfig(dpy, vis, GLX_GREEN_SIZE, &gbits);
	glXGetConfig(dpy, vis, GLX_BLUE_SIZE, &bbits);
	glXGetConfig(dpy, vis, GLX_DEPTH_SIZE, &zbits);
	glXGetConfig(dpy, vis, GLX_STENCIL_SIZE, &sbits);
	glXGetConfig(dpy, vis, GLX_STEREO, &stereo);
#if defined(GLX_VERSION_1_4) || defined(GLX_ARB_multisample)
	glXGetConfig(dpy, vis, GLX_SAMPLE_BUFFERS_ARB, &aa);
	if(aa) {
		glXGetConfig(dpy, vis, GLX_SAMPLES_ARB, &samples);
	}
#endif	/* defined GLX_VERSION_1_4 || GLX_ARB_multisample */

	gloin_log("got visual: %d%d%d d:%d s:%d", rbits, gbits, bbits, zbits, sbits);
	gloin_log(" %s", stereo ? "stereo" : "mono");
	gloin_log(" samples/pixel: %d\n", samples);
}

static int handle_event(XEvent *xev)
{
	int state;
	struct window *win;
	void (*func)();
	void *cls;
	KeySym sym;

	/* find the glory window corresponding to this X window */
	win = gloin_winlist();
	while(win) {
		if(win->wsys_win->win == xev->xany.window) {
			break;
		}
		win = win->next;
	}
	if(!win) {
		if(debug) {
			gloin_log("got event %s for unknown X window %d\n", xev_name(xev->type), (int)xev->xany.window);
		}
		return 0;
	}

	switch(xev->type) {
	case MapNotify:
		win->wsys_win->mapped = 1;
		break;

	case UnmapNotify:
		win->wsys_win->mapped = 0;
		break;

	case Expose:
		if(win->wsys_win->mapped && xev->xexpose.count == 0) {
			win->wsys_win->pending |= PENDING_REDISP;
		}
		break;

	case MotionNotify:
		if(xev->xmotion.state) {
			if((func = gloin_get_callback(win, GLORY_MOTION))) {
				cls = gloin_get_callback_data(win, GLORY_MOTION);
			}
		} else {
			if((func = gloin_get_callback(win, GLORY_PASSIVE))) {
				cls = gloin_get_callback_data(win, GLORY_PASSIVE);
			}
		}
		if(func) {
			win->mouse_index = 0;
			gloin_set_active(win);
			func(xev->xmotion.x, xev->xmotion.y, cls);
		}
		break;

	case ButtonPress:
		if(1) {
			state = 1;
		} else {
	case ButtonRelease:
			state = 0;
		}
		if((func = gloin_get_callback(win, GLORY_MOUSE))) {
			int bn = xev->xbutton.button - 1;
			win->mouse_index = 0;
			gloin_set_active(win);
			cls = gloin_get_callback_data(win, GLORY_MOUSE);
			func(bn, state, xev->xbutton.x, xev->xbutton.y, cls);
		}
		break;

	case KeyPress:
		if(1) {
			state = 1;
		} else {
	case KeyRelease:
			state = 0;
		}
		sym = XLookupKeysym(&xev->xkey, 0);
		process_key(sym, state);

		if((func = gloin_get_callback(win, GLORY_KEYBOARD))) {
			cls = gloin_get_callback_data(win, GLORY_KEYBOARD);
			gloin_set_active(win);
			func(translate_keysym(sym), state, cls);
		}
		break;

	case ConfigureNotify:
		if((func = gloin_get_callback(win, GLORY_RESHAPE))) {
			if(xev->xconfigure.width != win->wsys_win->width ||
					xev->xconfigure.height != win->wsys_win->height) {
				win->wsys_win->width = xev->xconfigure.width;
				win->wsys_win->height = xev->xconfigure.height;

				cls = gloin_get_callback_data(win, GLORY_RESHAPE);

				gloin_set_active(win);
				func(xev->xconfigure.width, xev->xconfigure.height, cls);
			}
		}
		break;

	case ClientMessage:
		if(xev->xclient.message_type == xa_wm_prot) {
			if(xev->xclient.data.l[0] == xa_wm_del_win) {
				/* call the public one, in order to also remove it from the list */
				glory_destroy_window(win->id);
			}
		}
		break;

	default:
		break;
	}

	return 0;
}

static void process_key(KeySym sym, int state)
{
	switch(sym) {
	case XK_Shift_L:
	case XK_Shift_R:
		modkeys = state ? (modkeys | GLORY_MOD_SHIFT) : (modkeys & ~GLORY_MOD_SHIFT);
		break;

	case XK_Control_L:
	case XK_Control_R:
		modkeys = state ? (modkeys | GLORY_MOD_CONTROL) : (modkeys & ~GLORY_MOD_CONTROL);
		break;

	case XK_Alt_L:
	case XK_Alt_R:
		modkeys = state ? (modkeys | GLORY_MOD_ALT) : (modkeys & ~GLORY_MOD_ALT);
		break;

	default:
		break;
	}
}

static int translate_keysym(KeySym sym)
{
	if(sym < 256) {
		if(isalpha(sym) && (modkeys & GLORY_MOD_SHIFT)) {
			sym = toupper(sym);
		}
		return sym;
	}

	switch(sym) {
	case XK_BackSpace:
		return '\b';
	case XK_Tab:
		return '\t';
	case XK_Linefeed:
		return '\r';
	case XK_Return:
		return '\n';
	case XK_Escape:
		return 27;
	default:
		break;
	}
	return (int)sym;
}

static long xevent_mask(unsigned int cbmask)
{
	long xmask = StructureNotifyMask | VisibilityChangeMask;

	if(cbmask & (1 << GLORY_DISPLAY)) {
		xmask |= ExposureMask;
	}
	if(cbmask & (1 << GLORY_KEYBOARD)) {
		xmask |= KeyPressMask | KeyReleaseMask;
	}
	if(cbmask & (1 << GLORY_MOUSE)) {
		xmask |= ButtonPressMask | ButtonReleaseMask;
	}
	if(cbmask & (1 << GLORY_MOTION)) {
		xmask |= ButtonMotionMask;
	}
	if(cbmask & (1 << GLORY_PASSIVE)) {
		xmask |= PointerMotionMask;
	}

	return xmask;
}


static int xrandr_resize(int xsz, int ysz, int rate)
{
#ifdef USE_XRANDR
	int event_base, error_base, ver_major, ver_minor, use_rate;
	XRRScreenConfiguration *xrr_config = 0;
	Status result = -1;
	Window root_win;

	/* must check at runtime for the availability of the extension */
	if(!XRRQueryExtension(dpy, &event_base, &error_base)) {
		return -1;
	}

	XRRQueryVersion(dpy, &ver_major, &ver_minor);

	/* we only heed the rate if we CAN actually use it (Xrandr >= 1.1) and
	 * the user actually cares about it (rate > 0)
	 */
	use_rate = rate > 0 && (ver_major > 1 || (ver_major == 1 && ver_minor >= 1));

	root_win = DefaultRootWindow(dpy);

	/* this loop is only so that the whole thing will be repeated if someone
	 * else changes video mode between our query of the current information and
	 * the attempt to change it.
	 */
	do {
		XRRScreenSize *ssizes;
		short *rates;
		Rotation rot;
		int i, ssizes_count, rates_count, curr, res_idx = -1;
		Time timestamp, cfg_timestamp;

		if(xrr_config) {
			XRRFreeScreenConfigInfo(xrr_config);
		}

		if(!(xrr_config = XRRGetScreenInfo(dpy, root_win))) {
			gloin_log("XRRGetScreenInfo failed\n");
			break;
		}
		ssizes = XRRConfigSizes(xrr_config, &ssizes_count);
		curr = XRRConfigCurrentConfiguration(xrr_config, &rot);
		timestamp = XRRConfigTimes(xrr_config, &cfg_timestamp);

		if(xsz == ssizes[curr].width && ysz == ssizes[curr].height) {
			/* no need to switch, we're already in the requested resolution */
			res_idx = curr;
		} else {
			for(i=0; i<ssizes_count; i++) {
				if(ssizes[i].width == xsz && ssizes[i].height == ysz) {
					res_idx = i;
					break;  /* found it */
				}
			}
		}
		if(res_idx == -1) {
			break;  /* no matching resolution */
		}

#if RANDR_MAJOR > 1 || (RANDR_MAJOR == 1 && RANDR_MINOR >= 1)
		if(use_rate) {
			/* for the selected resolution, let's find out if there is
			 * a matching refresh rate available.
			 */
			rates = XRRConfigRates(xrr_config, res_idx, &rates_count);

			for(i=0; i<rates_count; i++) {
				if(rates[i] == rate) {
					break;
				}
			}
			if(i == rates_count) {
				break; /* no matching rate */
			}

			result = XRRSetScreenConfigAndRate(dpy, xrr_config, root_win,
					res_idx, rot, rate, timestamp);
		} else
#endif
			result = XRRSetScreenConfig(dpy, xrr_config, root_win,
					res_idx, rot, timestamp);

	} while(result == RRSetConfigInvalidTime);

	if(xrr_config) {
		XRRFreeScreenConfigInfo(xrr_config);
	}

	if(result == 0) {
		return 0;
	}
#endif	/* USE_XRANDR */
	return -1;
}

static int xrandr_current(int *xsz, int *ysz, int *rate)
{
#ifdef USE_XRANDR
	Window root;
	int event_base, error_base;
	int ver_major, ver_minor;
	XRRScreenConfiguration *xrr_config;
	XRRScreenSize *ssizes;
	Rotation rot;
	int ssize_count, curr;

	root = DefaultRootWindow(dpy);

	/* must check at runtime for the availability of the extension */
	if(!XRRQueryExtension(dpy, &event_base, &error_base)) {
		return -1;
	}

	XRRQueryVersion(dpy, &ver_major, &ver_minor);

	if(!(xrr_config = XRRGetScreenInfo(dpy, root))) {
		gloin_log("failed to retrieve screen configuration\n");
		return -1;
	}
	ssizes = XRRConfigSizes(xrr_config, &ssize_count);
	curr = XRRConfigCurrentConfiguration(xrr_config, &rot);

	*xsz = ssizes[curr].width;
	*ysz = ssizes[curr].height;

#if RANDR_MAJOR > 1 || (RANDR_MAJOR == 1 && RANDR_MINOR >= 1)
	if(rate) {
		*rate = XRRConfigCurrentRate(xrr_config);
	}
#endif

	XRRFreeScreenConfigInfo(xrr_config);
	return 0;

#endif /* USE_XRANDR */
	return -1;
}

static int xf86vm_resize(int xsz, int ysz, int rate)
{
	return -1;	/* TODO */
}

static int xf86vm_current(int *xsz, int *ysz, int *rate)
{
	return -1;
}

static const char *evnames[] = {
	"<invalid event 0>", "<invalid event 1>",
	"KeyPress",
	"KeyRelease",
	"ButtonPress",
	"ButtonRelease",
	"MotionNotify",
	"EnterNotify",
	"LeaveNotify",
	"FocusIn",
	"FocusOut",
	"KeymapNotify",
	"Expose",
	"GraphicsExpose",
	"NoExpose",
	"VisibilityNotify",
	"CreateNotify",
	"DestroyNotify",
	"UnmapNotify",
	"MapNotify",
	"MapRequest",
	"ReparentNotify",
	"ConfigureNotify",
	"ConfigureRequest",
	"GravityNotify",
	"ResizeRequest",
	"CirculateNotify",
	"CirculateRequest",
	"PropertyNotify",
	"SelectionClear",
	"SelectionRequest",
	"SelectionNotify",
	"ColormapNotify",
	"ClientMessage",
	"MappingNotify",
	"GenericEvent"
};

static const char *xev_name(int type)
{
	return evnames[type];
}
